﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
using System.Xml.Linq;
using Cqrs.Infrastructure;
using Cqrs.StateMachineDesigner.CustomCode;
using Cqrs.StateMachineDesigner.Database;
using Cqrs.StateMachineDesigner.Resources;
using Microsoft.Win32;
using Cqrs.Common;
using Cqrs.Infrastructure.Aggregate;

namespace Cqrs.StateMachineDesigner
{
    public partial class DesignerCanvas
    {
        // todo - bit of a hack here - static vars?.
        public static List<DomainProperty> AllDomainProperties = new List<DomainProperty>();
        public static List<CommandEvent> AdHocMethods = new List<CommandEvent>();

        public static string DbLocation { get; set; }
        public static String SmfFile { get; set; }

        private ComponentModel _componentModel = new ComponentModel();

        public static RoutedCommand Debug = new RoutedCommand();
        public static RoutedCommand LoadAggregate = new RoutedCommand();
        public static RoutedCommand ExportToXml = new RoutedCommand();
        public static RoutedCommand ImportScxml = new RoutedCommand();
        public static RoutedCommand SaveToDb = new RoutedCommand();
        public static RoutedCommand LoadFromDb = new RoutedCommand();
        public static RoutedCommand GenerateCode = new RoutedCommand();
        public static RoutedCommand AddEditProperties = new RoutedCommand();
        public static RoutedCommand LoadComponent = new RoutedCommand();
        public static RoutedCommand SaveAs = new RoutedCommand();
        public static RoutedCommand AddAdHocCode = new RoutedCommand();

        public string AssemblyType { get; set; }
        public string AssemblyLocation { get; set; }

        public bool IsAssemblyLoaded
        {
            get { return (bool)GetValue(IsAssemblyLoadedProperty); }
            set { SetValue(IsAssemblyLoadedProperty, value); }
        }
        public static readonly DependencyProperty IsAssemblyLoadedProperty = DependencyProperty.Register("IsAssemblyLoaded", typeof(bool), typeof(DesignerCanvas));

        public string Details
        {
            get { return (string)GetValue(DetailsProperty); }
            set { SetValue(DetailsProperty, value); }
        }
        public static readonly DependencyProperty DetailsProperty = DependencyProperty.Register("Details", typeof(string), typeof(DesignerCanvas));

        public string DomainName
        {
            get { return (string)GetValue(DomainNameProperty); }
            set { SetValue(DomainNameProperty, value); }
        }
        public static readonly DependencyProperty DomainNameProperty =
            DependencyProperty.Register("DomainName", typeof(string), typeof(DesignerCanvas));

        public string Namespace
        {
            get { return (string)GetValue(NamespaceProperty); }
            set { SetValue(NamespaceProperty, value); }
        }
        public static readonly DependencyProperty NamespaceProperty = DependencyProperty.Register("Namespace", typeof(string), typeof(DesignerCanvas));

        public StateMachine DesignerStateMachine { get; set; }

        private void ExportToXml_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {
                SaveToScxmlFile();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }


        private bool ValidateStateChart()
        {
            IEnumerable<DesignerItem> designerItems = this.Children.OfType<DesignerItem>();

            // Find all States that have a State Type of Start
            if (designerItems.ToList().FindAll(p => p.StateType == StateType.Start).Count() > 1)
            {
                MessageBox.Show("There can only be one Start State in the State Chart.");
                return false;
            }
            var connections = this.Children.OfType<Connection>();
            int startStateTransitions = 0;
            int endStateTransitions = 0;
            foreach (Connection connection in connections)
            {
                if (connection.Source.ParentDesignerItem.StateType == StateType.Start)
                    startStateTransitions++;
                if (startStateTransitions > 1)
                {
                    MessageBox.Show("A Start State can be the source of only one Command/Event Transition.");
                    return false;
                }

                if (connection.Source.ParentDesignerItem.StateType == StateType.End)
                    endStateTransitions++;
                if (endStateTransitions > 0)
                {
                    MessageBox.Show("An End State cannot be the source of a Command/Event Transition.");
                    return false;
                }

            }

            return true;
        }

        private void ImportScxml_Executed(object sender, ExecutedRoutedEventArgs e)
        {

            try
            {
                OpenFileDialog openFile = new OpenFileDialog();
                openFile.Filter = "Scxml Files (*.xml)|*.xml|All Files (*.*)|*.*";

                if (openFile.ShowDialog() == true)
                {
                    string fileName = openFile.FileName;
                    this.DomainName = openFile.SafeFileName.Replace(".xml", "");

                    // todo - can't import a file that contains "x<0"
                    var xDoc = XDocument.Load(fileName);

                    XElement scXml = XElement.Parse(xDoc.ToString());
                    Dictionary<string, Guid> states = new Dictionary<string, Guid>();

                    double top = 10;
                    double left = 10;

                    string initialState = "";
                    if (scXml.Attribute("initial") != null)
                    {
                        initialState = scXml.Attribute("initial").Value;
                    }

                    foreach (var state in scXml.Elements())
                    {
                        // todo - refactor this code
                        StateType stateType = StateType.State;
                        string stateName = "";
                        if (state.Attribute("id") != null)
                        {
                            stateName = state.Attribute("id").Value;
                        }
                        else
                            continue;

                        XElement gridXElement;
                        if (initialState == stateName)
                        {
                            gridXElement = XElement.Parse(IconResources.StartGridXml);
                            stateType = StateType.Start;
                        }
                        else
                        {
                            if (state.Name.LocalName == "Final")
                            {
                                gridXElement = XElement.Parse(IconResources.EndGridXml);
                                stateType = StateType.End;
                            }
                            else
                                gridXElement = XElement.Parse(IconResources.StateGridXml);
                        }

                        DesignerItem newState = AddStateItemToCanvas(Guid.NewGuid(), stateType,
                                                state.Attribute("id").Value, top, left, gridXElement);
                        states.Add(newState.DisplayText, newState.ID);

                        top += 100;
                        left += 100;
                    }

                    foreach (var state in scXml.Elements())
                    {
                        // todo - refactor this code
                        string stateName = "";
                        if (state.Attribute("id") != null)
                        {
                            stateName = state.Attribute("id").Value;
                        }
                        else
                            continue;

                        foreach (var transition in state.Elements())
                        {
                            // todo - refactor this code
                            string transitionName = "";
                            if (transition.Attribute("event") != null)
                            {
                                transitionName = transition.Attribute("event").Value;
                            }
                            else
                                continue;

                            // todo - refactor this code
                            string targetName = "";
                            if (transition.Attribute("target") != null)
                            {
                                targetName = transition.Attribute("target").Value;
                            }
                            else
                                continue;

                            ConnectDesignerItems(transitionName, 1, states[stateName], states[targetName],
                                ConnectorOrientation.Right.ToString(), ConnectorOrientation.Left.ToString(), "", null);
                        }
                    }

                }

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

        }

        private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {
                if (!ValidateStateChart()) return;

                if (string.IsNullOrWhiteSpace(DesignerCanvas.SmfFile))
                    SaveAs_Executed(sender, e);
                else
                    SaveSmfFile(DesignerCanvas.SmfFile);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void LoadComponent_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {
                OpenComponent();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        private void AddAdHocCode_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            AdHocMethods adHocMethods = new AdHocMethods();
            try
            {
                DesignerCanvas.AdHocMethods = adHocMethods.ShowForm(
                    DesignerCanvas.AdHocMethods, DesignerCanvas.AllDomainProperties);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
            finally
            {
                adHocMethods = null;
            }
        }


        private void AddEditProperties_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            List<Tuple<string, string>> events = new List<Tuple<string, string>>();
            DomainProperties properties = new DomainProperties();
            properties.Owner = Application.Current.MainWindow;
            try
            {
                DomainProperties.DomainPropertyModel domainPropertyModel = new DomainProperties.DomainPropertyModel();

                List<DomainProperty> originalDomainProperties = new List<DomainProperty>(AllDomainProperties);
                domainPropertyModel.DomainProperties = AllDomainProperties;
                domainPropertyModel.Name = this.DomainName;
                domainPropertyModel.Namespace = this.Namespace;

                domainPropertyModel = properties.ShowForm(domainPropertyModel);
                if (domainPropertyModel != null)
                {
                    AllDomainProperties = domainPropertyModel.DomainProperties;
                    this.DomainName = domainPropertyModel.Name;
                    this.Namespace = domainPropertyModel.Namespace;

                    IEnumerable<Connection> connections = this.Children.OfType<Connection>();
                    foreach (var connection in connections)
                    {
                        foreach (DomainProperty originalProperty in originalDomainProperties)
                        {
                            // if a property has been removed:
                            if (!AllDomainProperties.Exists(p => p.Name == originalProperty.Name))
                            {
                                connection.DomainProperties.RemoveAll(
                                    p => p.Name == originalProperty.Name
                                    );
                            }
                        }

                        foreach (DomainProperty property in AllDomainProperties)
                        {
                            // if a DomainProperty is to be assigned to all Commands/Events:
                            if (property.ApplyToAllCommandsEvents)
                            {
                                if (!connection.DomainProperties.Exists(p => p.Name == property.Name))
                                    connection.DomainProperties.Add(property);
                            }
                        }
                    }


                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
            finally
            {
                if (properties != null)
                {
                    properties.Close();
                    properties = null;
                }
            }
        }

        private void GenerateCode_Executed(object sender, ExecutedRoutedEventArgs e)
        {

            try
            {
                StateMachine stateMachine = BuildStateMachine();

                Generator.CodeGenerator codeGen = new Generator.CodeGenerator();
                codeGen.Owner = Application.Current.MainWindow;

                codeGen.ShowForm(stateMachine, Namespace, DomainName, DesignerCanvas.AllDomainProperties, 
                    DesignerCanvas.AdHocMethods,  DesignerCanvas.SmfFile);

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        private DesignerItem AddStateItemToCanvas(Guid id, StateType stateType, string displayText, double top, double left, XElement content)
        {
            XElement stateItem = XElement.Parse(IconResources.DesignerItemXml);
            stateItem.SetElementValue("Top", top);
            stateItem.SetElementValue("Left", left);
            stateItem.SetElementValue("ID", id);

            stateItem.Add(content);
            DesignerItem item = AddDesignerItemToCanvas(stateItem, stateType);
            item.DisplayText = displayText;
            return item;

        }

        private StateMachine BuildStateMachine()
        {
            IEnumerable<DesignerItem> designerItems = this.Children.OfType<DesignerItem>();
            IEnumerable<Connection> connections = this.Children.OfType<Connection>();
            StateMachine states = new StateMachine();

            foreach (var designerItem in designerItems)
            {
                Dictionary<string, Transition> transitions = new Dictionary<string, Transition>();
                if (designerItem.StateType == StateType.Start)
                {
                    states.InitialState = designerItem.DisplayText;
                }

                if (designerItem.StateType == StateType.End)
                {
                    states.FinalState = designerItem.DisplayText;
                }

                states.Add(designerItem.DisplayText, new State(designerItem.DisplayText));
                states[designerItem.DisplayText].Transitions = transitions;
            }

            foreach (var connection in connections)
            {
                Transition transition = new Transition();
                transition.Event = connection.Event;
                transition.Command = connection.Command;
                transition.ResultingState = connection.Sink.ParentDesignerItem.DisplayText;
                transition.AffectedProperties = connection.DomainProperties;

                DesignerItem sourceState = connection.Source.ParentDesignerItem;
                try
                {
                    // Get the key (a State object) for the current source State
                    //var state = states.Where(p => p.Key.Name == sourceState.DisplayText).FirstOrDefault().Key;
                    states[sourceState.DisplayText].Transitions.Add(connection.Event, transition);
                }
                catch (ArgumentException)
                {

                    throw new ApplicationException(
                        string.Format("The '{0}' State is a source multiple Events of type '{1}'.  Please rename one of the existing events.",
                        sourceState.DisplayText,
                        connection.Event));
                }
                catch (Exception ex) { throw ex; }
            }
            return states;
        }

        private void SaveToScxmlFile()
        {
            string fileName;
            SaveFileDialog saveFile = new SaveFileDialog();
            saveFile.Filter = "Scxml Files | *.xml";
            if (saveFile.ShowDialog() == true)
                fileName = saveFile.FileName;
            else
                return;

            GetScxmlFile().Save(fileName, SaveOptions.None);

        }

        private XElement GetScxmlFile()
        {
            StateMachine states = BuildStateMachine();

            XDocument xdoc = new XDocument(
                new XDeclaration("1.0", "utf-8", "yes"),
                new XElement("Root", "Content")
            );

            XNamespace ns = "http://www.w3.org/2005/07/scxml";
            XElement stateMachineXml = new XElement(ns + "scxml");
            stateMachineXml.Add(new XAttribute("version", "1.0"));

            foreach (var state in states)
            {
                string stateType = string.Equals(states.FinalState, state.Key, StringComparison.OrdinalIgnoreCase)
                    ? "Final" : "State";

                XElement stateXml = new XElement(stateType);
                stateXml.Add(new XAttribute("id", state.Key));
                if (string.Equals(states.InitialState, state.Key, StringComparison.OrdinalIgnoreCase))
                {
                    stateMachineXml.Add(new XAttribute("initial", state.Key));
                }
                foreach (var transition in state.Value.Transitions)
                {
                    XElement transitionXml = new XElement("transition");
                    transitionXml.Add(new XAttribute("command", transition.Value.Command));
                    transitionXml.Add(new XAttribute("event", transition.Value.Event));
                    transitionXml.Add(new XAttribute("target", transition.Value.ResultingState));
                    stateXml.Add(transitionXml);
                }
                stateMachineXml.Add(stateXml);
            }

            // remove instances of xmlns="" from each node.
            foreach (XElement e in stateMachineXml.DescendantsAndSelf())
            {
                if (e.Name.Namespace == "")
                {
                    e.Name = ns + e.Name.LocalName;
                }
            }

            return stateMachineXml;

        }

        //private void SaveToDb_Executed(object sender, ExecutedRoutedEventArgs e)
        //{
        //    Database.SaveLoadToDatabase dbForm = new Database.SaveLoadToDatabase();

        //    StateMachineRecord stateMachine = new StateMachineRecord()
        //    {
        //        Name = StateMachineName,
        //        AssemblyType = AssemblyType,
        //        AssemblyLocation = AssemblyLocation,
        //        StateMachineDesigner = GetSmdFile().ToString(),
        //        StateMachineXml = GetScxmlFile().ToString()
        //    };

        //    stateMachine = dbForm.SaveStateMachine(stateMachine);
        //    SetDesignerValues(stateMachine);

        //}

        //private void LoadFromDb_Executed(object sender, ExecutedRoutedEventArgs e)
        //{
        //    Database.SaveLoadToDatabase dbForm = new Database.SaveLoadToDatabase();
        //    StateMachineRecord stateMachine = dbForm.LoadStateMachine();
        //    if (stateMachine != null)
        //    {
        //        OpenSmd(XElement.Parse(stateMachine.StateMachineDesigner));
        //        SetDesignerValues(stateMachine);
        //    }
        //}

        private void OpenSmf()
        {
            string smfFile = GetSmfFile();
            if (string.IsNullOrEmpty(smfFile)) return;
            Reset();
            SmfFileParser parser = new SmfFileParser(smfFile);
            DesignerCanvas.AllDomainProperties = DomainProperty.ConvertXElementsToDomainProperties(
                                                    parser.GetDomainProperties());

            DesignerCanvas.AdHocMethods = CommandEvent.ConvertXElementsToCommandEvent(
                                        parser.GetAdHocMethods());

            this.DomainName = parser.GetDomainName();
            this.Namespace = parser.GetNamespace();
            this.AssemblyLocation = parser.GetAssemblyLocation();
            this.AssemblyType = parser.GetDomainType();
            DesignerCanvas.SmfFile = smfFile;

            SetDetails();

            XElement designerXml = parser.GetStateMachineDesigner();
            if (designerXml == null)
            {
                throw new ApplicationException("No designer Xml exists");
            }
            OpenDesignerXml(designerXml);
        }

        private void OpenComponent()
        {
            CustomCode.LoadComponent loadComponent = new CustomCode.LoadComponent();
            loadComponent.Owner = Application.Current.MainWindow;

            try
            {
                ComponentModel returnedModel = loadComponent.LoadComponentModel(_componentModel);
                if (returnedModel == null) return;
                _componentModel = returnedModel;

                Reset();

                DesignerCanvas.DbLocation = _componentModel.DbLocation;
                DesignerCanvas.SmfFile = _componentModel.SmfFile;
                this.DomainName = _componentModel.DomainName;
                this.AssemblyLocation = _componentModel.AssemblyLocation;
                this.AssemblyType = _componentModel.AssemblyType;

                SetDetails();

                DesignerCanvas.AllDomainProperties = _componentModel.DomainProperties;
                if (_componentModel.DesignerXml == null)
                {
                    throw new ApplicationException("No designer Xml exists");
                }
                OpenDesignerXml(_componentModel.DesignerXml);
                this.IsAssemblyLoaded = true;
            }
            finally
            {
                loadComponent.Close();
            }
        }

        private void SetDetails()
        {

            this.Details = string.IsNullOrEmpty(DesignerCanvas.SmfFile) ? "" : "Smf File: \t" + DesignerCanvas.SmfFile + "\n";
            this.Details += string.IsNullOrEmpty(this.AssemblyType) ? "" : "Assembly: \t" + this.AssemblyType + "\n";
            this.Details += string.IsNullOrEmpty(this.AssemblyLocation) ? "" : "Location: \t" + this.AssemblyLocation + "\n";
            this.Details += string.IsNullOrEmpty(DesignerCanvas.DbLocation) ? "" : "Database Folder: \t" + DesignerCanvas.DbLocation;
        }


        private void Reset()
        {
            this.Children.Clear();
            this.SelectionService.ClearSelection();
            this.DomainName = "[untitled]";
            this.Namespace = "";
            this.Details = "";
            this.AssemblyLocation = "";
            this.AssemblyType = "";
            DesignerCanvas.AllDomainProperties.Clear();
            DesignerCanvas.SmfFile = "";
            DesignerCanvas.DbLocation = "";
            IsAssemblyLoaded = false;
        }

        public void HighlightStates(List<string> highlightStates)
        {
            foreach (string state in highlightStates)
            {
                // todo - need to prevent user saving a state machine with duplicate states.
                foreach (var child in this.Children)
                {
                    DesignerItem stateItem = child as DesignerItem;
                    if (stateItem != null)
                    {
                        if (state.Equals(stateItem.DisplayText, StringComparison.OrdinalIgnoreCase))
                            stateItem.Highlight();
                        else
                            stateItem.RemoveHighlight();
                    }
                }
            }
        }

        public void HighlightTransitions(List<HighlightTransition> highlightTransitions)
        {
            foreach (HighlightTransition highlightTransition in highlightTransitions)
            {
                // todo - need to prevent user saving a state machine with duplicate states.
                foreach (var child in this.Children)
                {
                    Connection connection = child as Connection;
                    if (connection != null)
                    {
                        if (highlightTransition.EventName.Equals(connection.Event, StringComparison.OrdinalIgnoreCase))
                            if (connection.Source.ParentDesignerItem.DisplayText.Equals(highlightTransition.PreviousState, StringComparison.OrdinalIgnoreCase))
                                connection.Highlight();
                    }
                }
            }
        }

        public void RemoveAllHighlights()
        {
            foreach (var child in this.Children)
            {
                Connection connection = child as Connection;
                if (connection != null)
                {
                    connection.RemoveHighlight();
                }
                DesignerItem stateItem = child as DesignerItem;
                if (stateItem != null)
                {
                    stateItem.RemoveHighlight();
                }
            }
        }

    }

}
